﻿using System;
using Dokan;
using System.Threading;
using System.IO;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
namespace Dokan
{
    /// <summary>
    /// A base class for creating Dokan Drives.  This base class ensures drives are unmounted
    /// when the program exits as long as the process is allowed to exit normally.  If you kill
    /// the process via task manager or debugger the drive will not be unmounted
    /// </summary>
    public abstract class BaseDokanDrive : DokanOperations, IDisposable
    {        
        /// <summary>
        /// Creates a Dokan Drive with the specified drive letter
        /// </summary>
        public BaseDokanDrive(char driveLetter, String volumeLabel)
            : this(driveLetter, volumeLabel, 0) { Contract.Requires(volumeLabel != null); }
        /// <summary>
        /// Creates a Dokan Drive with the specified driver letter and threadcount
        /// </summary>
        /// <remarks>Set threadcount to 0 to let the system decide the number of threads it needs (recomended)</remarks>
        public BaseDokanDrive(char driveLetter, String volumeLabel, ushort threadCount)
            : this(driveLetter, volumeLabel, threadCount, false) { Contract.Requires(volumeLabel != null); }
        /// <summary>
        /// Creates a Dokan Drive with the specified driver letter, threadcount, and debug mode
        /// </summary>
        /// <remarks>Set threadcount to 0 to let the system decide the number of threads it needs (recomended).  I don't know what debugmode does</remarks>
        public BaseDokanDrive(char driveLetter, String VolumeLabel, ushort threadCount, bool debugMode)
        {
            Contract.Requires(VolumeLabel != null);
            Contract.Requires(threadCount >= 0);

            _driveLetter = driveLetter;
            _threadCount = threadCount;
            _debugMode = debugMode;
            _volumeLabel = VolumeLabel;
        }

        /// <summary>
        /// Unmounts the drive
        /// </summary>
        ~BaseDokanDrive()
        {
            Unmount();
        }
        /// <summary>
        /// Unmounts the drive
        /// </summary>
        public void Dispose()
        {
            Unmount();
            GC.SuppressFinalize(this);
            DoDispose();
        }
        /// <summary>
        /// Override this function if you need to do your own disposing too
        /// </summary>
        /// <remarks>
        /// Drive has already been unmounted by the time this gets called
        /// </remarks>
        protected virtual void DoDispose()
        {}

        /// <summary>
        /// Creates a new master thread and mounts the drive in it
        /// </summary>
        public void Start()
        {
            lock (synclock)
            {
                if (!_isMounted)
                {
                    _isMounted = true;
                    myThread = new Thread(new ThreadStart(Mount));
                    myThread.IsBackground = true;
                    myThread.Start();
                }
            }
        }
        /// <summary>
        /// Unmounts the drive
        /// </summary>
        public void Stop()
        { Unmount(); }

        /// <summary>
        /// Does the actual mounting
        /// </summary>
        private void Mount()
        {
            DokanOptions opt = new DokanOptions();
            opt.DebugMode = _debugMode;
            opt.DriveLetter = _driveLetter;
            opt.ThreadCount = _threadCount;
            opt.VolumeLabel = _volumeLabel;
            DokanNet.DokanMain(opt, this);
            lock (synclock)
            {
                _isMounted = false;
                myThread = null;
            }
        }
        /// <summary>
        /// Does the actual unmounting, calling unmount immedietly after mount may result in the unmount being ignored
        /// </summary>
        private void Unmount()
        {
            if (_isMounted)
            {
                DokanNet.DokanUnmount(_driveLetter);
            }
        }

        private char _driveLetter;
        private ushort _threadCount;
        private bool _debugMode;
        private String _volumeLabel;

        private object synclock = new object();
        private Thread myThread;
        private bool _isMounted = false;

        /// <summary>
        /// Indicates that the Drive is currently starting or started
        /// </summary>
        /// <remarks>It may take a second after starting for the mounted device to become visible</remarks>
        public bool IsStarted
        { get { return _isMounted; } }


        #region Dokan Interface Functions
        public virtual int CreateFile(String filename, FileAccess access, FileShare share,
            FileMode mode, FileOptions options, DokanFileInfo info)
        { return -DokanNet.ERROR_ACCESS_DENIED; }
      
        public virtual int OpenDirectory(String filename, DokanFileInfo info)
        { return -DokanNet.ERROR_ACCESS_DENIED; }

        public virtual int CreateDirectory(String filename, DokanFileInfo info)
        { return -DokanNet.ERROR_ACCESS_DENIED; }

        public virtual int Cleanup(String filename, DokanFileInfo info)
        { return 0; }

        public virtual int CloseFile(String filename, DokanFileInfo info)
        { return 0; }

        public virtual int ReadFile(String filename, Byte[] buffer, ref uint readBytes,
            long offset, DokanFileInfo info)
        { return -DokanNet.ERROR_ACCESS_DENIED; }

        public virtual int WriteFile(String filename, Byte[] buffer,
            ref uint writtenBytes, long offset, DokanFileInfo info)
        { return -DokanNet.ERROR_ACCESS_DENIED; }

        public virtual int FlushFileBuffers(String filename, DokanFileInfo info)
        { return -DokanNet.ERROR_ACCESS_DENIED; }

        public virtual int GetFileInformation(String filename, FileInformation fileinfo, DokanFileInfo info)
        { return -DokanNet.ERROR_ACCESS_DENIED; }

        public virtual int FindFiles(String filename, IList<FileInformation> files, DokanFileInfo info)
        { return -DokanNet.ERROR_ACCESS_DENIED; }

        public virtual int SetFileAttributes(String filename, FileAttributes attr, DokanFileInfo info)
        { return -DokanNet.ERROR_ACCESS_DENIED; }

        public virtual int SetFileTime(String filename, DateTime ctime, DateTime atime, DateTime mtime, DokanFileInfo info)
        { return -DokanNet.ERROR_ACCESS_DENIED; }

        public virtual int DeleteFile(String filename, DokanFileInfo info)
        { return -DokanNet.ERROR_ACCESS_DENIED; }

        public virtual int DeleteDirectory(String filename, DokanFileInfo info)
        { return -DokanNet.ERROR_ACCESS_DENIED; }

        public virtual int MoveFile(String filename, String newname, bool replace, DokanFileInfo info)
        { return -DokanNet.ERROR_ACCESS_DENIED; }

        public virtual int SetEndOfFile(String filename, long length, DokanFileInfo info)
        { return -DokanNet.ERROR_ACCESS_DENIED; }

        public virtual int LockFile(String filename, long offset, long length, DokanFileInfo info)
        { return 0; }

        public virtual int UnlockFile(String filename, long offset, long length, DokanFileInfo info)
        { return 0; }

        public virtual int GetDiskFreeSpace(ref ulong freeBytesAvailable, ref ulong totalBytes,
            ref ulong totalFreeBytes, DokanFileInfo info)
        { return -DokanNet.ERROR_ACCESS_DENIED; }

        public virtual int Unmount(DokanFileInfo info)
        { return 0; }

        public int SetAllocationSize(string filename, long length, DokanFileInfo info)
        { return 0; }

        #endregion
    }
}
